﻿//===============================================================================
// Microsoft patterns & practices
// Windows Phone 7 Developer Guide
//===============================================================================
// Copyright © Microsoft Corporation.  All rights reserved.
// This code released under the terms of the 
// Microsoft patterns & practices license (http://wp7guide.codeplex.com/license)
//===============================================================================


using System.Diagnostics;

namespace AzureToolkit
{
    using System;
    using System.Collections.Generic;
    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.StorageClient;
    using System.Runtime.Serialization.Formatters;
    using System.Diagnostics.Contracts;
    using Microsoft.WindowsAzure.ServiceRuntime;

    public class AzureQueue<TMessage> : IAzureQueue<TMessage>, IInitializer where TMessage : IAzureQueueMessage
    {

        private readonly CloudStorageAccount account;
        private readonly TimeSpan visibilityTimeout;
        private readonly CloudQueue queue;
       
        public AzureQueue()
            : this(CloudConfiguration.GetStorageAccount())
        {
        }

        public AzureQueue(CloudStorageAccount account)
            : this(account, TimeSpan.FromMinutes(5))
        {
            Contract.Requires(account != null);
        }

        public AzureQueue(CloudStorageAccount account, string queueName)
            : this(account, queueName, TimeSpan.FromMinutes(5))
        {
            Contract.Requires(account != null);
            Contract.Requires(visibilityTimeout != null);
        }

        public AzureQueue(CloudStorageAccount account, TimeSpan visibilityTimeout)
            : this(account, null, visibilityTimeout)
        {
            Contract.Requires(account != null);
            Contract.Requires(visibilityTimeout != null);
        }

        public AzureQueue(CloudStorageAccount account, string queueName, TimeSpan visibilityTimeout)
        {
            Contract.Requires(account != null);
            Contract.Requires(visibilityTimeout != null);
            Contract.Ensures(this.queue != null);
            Contract.Ensures(this.visibilityTimeout != null);
            Contract.Ensures(this.account != null);

            if (String.IsNullOrEmpty(queueName))
            {
                queueName = typeof(TMessage).Name.ToLower();
                if (queueName.EndsWith("message") && queueName.Length > 7)
                {
                    queueName = queueName.Substring(0, queueName.Length - 7);
                }
            }

            this.account = account;
            this.visibilityTimeout = visibilityTimeout;

            var client = this.account.CreateCloudQueueClient();
            if (client == null)
            {
                throw new InvalidOperationException("Client is null.");
            }

            this.queue = client.GetQueueReference(queueName.ToLowerInvariant());
       }

        public void AddMessage(TMessage message)
        {
            string serializedMessage = JsonSerializer.Current.SerializeObject(message);
            this.queue.AddMessage(new CloudQueueMessage(serializedMessage));
        }

        public void AddMessageAsync(TMessage message)
        {
            string serializedMessage = JsonSerializer.Current.SerializeObject(message);
            this.queue.BeginAddMessage(new CloudQueueMessage(serializedMessage), (ar) =>
            {
                var q = ar.AsyncState as CloudQueue;
                Exception error = null;
                try
                {
                    q.EndAddMessage(ar);
                }
                catch (Exception ex)
                {
                    error = ex;
                }

                if (AddMessageCompleted != null)
                {
                    var args = new QueueMessageEventArgs<TMessage>(error, false, null, message);
                    AddMessageCompleted(this, args);
                }
            }, this.queue);
        }

        public IAsyncResult BeginAddMessage(TMessage message, AsyncCallback callback, object state)
        {
            string serializedMessage = JsonSerializer.Current.SerializeObject(message);
            return this.queue.BeginAddMessage(new CloudQueueMessage(serializedMessage), callback, state);
        }

        public void EndAddMessage(IAsyncResult asyncResult)
        {
            this.queue.EndAddMessage(asyncResult);
        }

        public TMessage GetMessage()
        {
            var message = this.queue.GetMessage(this.visibilityTimeout);

            if (message == null)
            {
                return default(TMessage);
            }

            return GetDeserializedMessage(message);
        }

        public IList<TMessage> GetMessages(int maxMessagesToReturn)
        {
            var messages = this.queue.GetMessages(maxMessagesToReturn, this.visibilityTimeout);

            List<TMessage> list = new List<TMessage>();
            foreach (var message in messages)
            {
                list.Add(GetDeserializedMessage(message));
            }
            return list;
        }

        public int RetreiveApproximateMessageCount()
        {
            return this.queue.RetrieveApproximateMessageCount();
        }

        public void Initialize()
        {
            if (RoleEnvironment.IsAvailable)
            {
                this.queue.CreateIfNotExist();
            }
           
        }

        public void Clear()
        {
            this.queue.Clear();
        }

        public void DeleteMessage(TMessage message)
        {
            this.queue.DeleteMessage(message.Id, message.PopReceipt);
        }

        public void DeleteMessageAsync(TMessage message)
        {
            this.queue.BeginDeleteMessage(message.Id, message.PopReceipt, (ar) =>
            {
                this.queue.EndDeleteMessage(ar);
            }, null);
        }

        private TMessage GetDeserializedMessage(CloudQueueMessage message)
        {
            var deserializedMessage = JsonSerializer.Current.DeserializeObject<TMessage>(message.AsString);
            deserializedMessage.Id = message.Id;
            deserializedMessage.PopReceipt = message.PopReceipt;
            deserializedMessage.DequeueCount = message.DequeueCount;

            return deserializedMessage;
        }

        public void ErrorOutMessage(TMessage message, Exception ex)
        {
            string instanceId = null;
            try
            {
                var currentRoleInstance = Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.CurrentRoleInstance;
                if (currentRoleInstance != null)
                {
                    instanceId = currentRoleInstance.Id;
                }
            }
            catch
            {
                // Ignore this.
            }

            //var errorObj = new QueueError<TMessage>();
            //errorObj.Error = ex;
            //errorObj.InstanceId = instanceId;
            //errorObj.QueueMessage = message;
            //errorObj.Timestamp = DateTime.UtcNow;
            //errorObj.DequeueCount = message.DequeueCount;
            //errorObj.Id = message.Id;
            //errorObj.PopReceipt = message.PopReceipt;
        }

        [ContractInvariantMethod]
        private void Invariant()
        {
            Contract.Invariant(this.queue != null);
            Contract.Invariant(this.visibilityTimeout != null);
            Contract.Invariant(this.account != null);
        }


        public event EventHandler<QueueMessageEventArgs<TMessage>> AddMessageCompleted;
    }
}